﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Collections;

namespace PLCTools.Driver
{
    public enum VECommand
    {
        ReadDBBit, ReadDBBytes, WriteDBBit, WriteDBBytes, AB_ReadSingle, AB_WriteSingle
    }

    public abstract class PLCDriver
    {
        public bool IsConnected = false;
        public abstract bool Connect(string ip, string param);
        public abstract void Disconnect();
        public abstract bool ReadBytes(object addr, object[] data, DataType type, out byte[] buf);
        public abstract bool WriteBytes(object addr, object[] data, DataType type);
        public abstract bool ReadBytesPLCDB(int DBNumber, int start, byte[] buf);
        public abstract bool WriteBytesPLCDB(int DBNumber, int start, byte[] buf);
        public abstract bool WriteBitPLCDB(int DBNumber, int pos, bool val);
        public abstract string ToString();

        private Random _rnd = new Random();
        public enum DataType
        {
            UNKNOWN,
            AB_BOOL,    //bit
            AB_DINT,    //int32
            AB_INT,     //int16
            AB_REAL,    //float
            AB_SINT,    //byte
            AB_STRING,  //ASCII  
            MIT_WORD,   //1WORD uint16 
            MIT_INT,    //1WORD uint16
            MIT_LONG,    //2WORD uint32
            MIT_FLOAT,  //2WORD float
            MIT_CHAR2,   //1WORD int16
            SIE_BYTE,   //byte
            SIE_INT,    //int16
            SIE_UINT,    //uint16
            SIE_DINT,   //int32
            SIE_UDINT,   //uint32
            SIE_REAL,   //float
            SIE_CHAR,   //byte
            SIE_BOOL,    //bit
            OMR_WORD,    //1 WORD uint16 
            Bechoff_BOOL, //bool
            Bechoff_DINT,  //int32
            Bechoff_REAL,  //float
            Bechoff_STRING, // string
            Fanuc_BYTE,     //byte
            Fanuc_FLOAT,     //float
            OPC_BYTE,        //byte
            OPC_SHORT,       //int16
            OPC_BOOLEAN,     //bool
            OPC_STRING,      //string
            OPC_FLOAT,       //float
            OPC_FLOAT_ARRAY,   //float array
            OPC_BYTE_ARRAY,  //byte array

            Modbus_Coil_BOOL,  // Read write bool
            Modbus_DiscreteInput_BOOL,  // Read write bool
            Modbus_HoldingRegister_BOOL, // Read write bool
            Modbus_InputRegister_BOOL, // Read only bool
            Modbus_HoldingRegister_WORD, // Read write WORD
            Modbus_InputRegister_WORD    // Read only WORD
        }

        public enum PLCType
        {
            Allen_Bradley, Siemens, Mitsubishi, Omron, Bechkoff, MicroLogix, Fanuc, OPC, Modbus
        }

        public static Hashtable ABDataTypeTable = new Hashtable();
        public static Hashtable MitDataTypeTable = new Hashtable();
        public static Hashtable SieDataTypeTable = new Hashtable();
        public static Hashtable OmrDataTypeTable = new Hashtable();
        public static Hashtable BechoffDataTypeTable = new Hashtable();
        public static Hashtable MicroDataTypeTable = new Hashtable();
        public static Hashtable FanucDataTypeTable = new Hashtable();
        public static Hashtable OPCDataTypeTable = new Hashtable();
        public static Hashtable ModbusDataTypeTable = new Hashtable();

        protected object _synLock = new object();

        static PLCDriver()
        {
            ABDataTypeTable.Add(DataType.AB_BOOL, "BOOL");
            ABDataTypeTable.Add(DataType.AB_DINT, "DINT");
            ABDataTypeTable.Add(DataType.AB_INT, "INT");
            ABDataTypeTable.Add(DataType.AB_REAL, "REAL");
            ABDataTypeTable.Add(DataType.AB_SINT, "SINT");
            ABDataTypeTable.Add(DataType.AB_STRING, "STRING");
            MitDataTypeTable.Add(DataType.MIT_WORD, "WORD");
            MitDataTypeTable.Add(DataType.MIT_INT, "INT");
            MitDataTypeTable.Add(DataType.MIT_LONG, "LONG");
            MitDataTypeTable.Add(DataType.MIT_FLOAT, "FLOAT");
            MitDataTypeTable.Add(DataType.MIT_CHAR2, "CHAR2");
            SieDataTypeTable.Add(DataType.SIE_BYTE, "BYTE");
            SieDataTypeTable.Add(DataType.SIE_INT, "INT");
            SieDataTypeTable.Add(DataType.SIE_UINT, "UINT");
            SieDataTypeTable.Add(DataType.SIE_DINT, "DINT");
            SieDataTypeTable.Add(DataType.SIE_UDINT, "UDINT");
            SieDataTypeTable.Add(DataType.SIE_REAL, "REAL");
            SieDataTypeTable.Add(DataType.SIE_CHAR, "CHAR");
            SieDataTypeTable.Add(DataType.SIE_BOOL, "BOOL");
            OmrDataTypeTable.Add(DataType.OMR_WORD, "BYTE");
            BechoffDataTypeTable.Add(DataType.Bechoff_BOOL, "BOOL");
            BechoffDataTypeTable.Add(DataType.Bechoff_DINT, "DINT");
            BechoffDataTypeTable.Add(DataType.Bechoff_REAL, "REAL");
            BechoffDataTypeTable.Add(DataType.Bechoff_STRING, "STRING");
            FanucDataTypeTable.Add(DataType.Fanuc_BYTE, "BYTE");
            FanucDataTypeTable.Add(DataType.Fanuc_FLOAT, "FLOAT");
            OPCDataTypeTable.Add(DataType.OPC_BOOLEAN, "BOOL");
            OPCDataTypeTable.Add(DataType.OPC_BYTE, "BYTE");
            OPCDataTypeTable.Add(DataType.OPC_BYTE_ARRAY, "BYTE_ARRAY");
            OPCDataTypeTable.Add(DataType.OPC_FLOAT, "FLOAT");
            OPCDataTypeTable.Add(DataType.OPC_FLOAT_ARRAY, "FLOAT_ARRAY");
            OPCDataTypeTable.Add(DataType.OPC_SHORT, "SHORT");
            OPCDataTypeTable.Add(DataType.OPC_STRING, "STRING");

            ModbusDataTypeTable.Add(DataType.Modbus_Coil_BOOL, "CBOOL");
            ModbusDataTypeTable.Add(DataType.Modbus_DiscreteInput_BOOL, "DIBOOL");
            ModbusDataTypeTable.Add(DataType.Modbus_HoldingRegister_BOOL, "HRBOOL");
            ModbusDataTypeTable.Add(DataType.Modbus_InputRegister_BOOL, "IRBOOL");
            ModbusDataTypeTable.Add(DataType.Modbus_HoldingRegister_WORD, "HRWORD");
            ModbusDataTypeTable.Add(DataType.Modbus_InputRegister_WORD, "IRWORD");


        }

        public object GetObjectFromDataType(DataType type, string data)
        {
            object obj = new object();
            if (type == DataType.AB_BOOL || type == DataType.SIE_BOOL || type == DataType.Bechoff_BOOL || type == DataType.OPC_BOOLEAN ||
                type == DataType.Modbus_Coil_BOOL || type == DataType.Modbus_DiscreteInput_BOOL || type == DataType.Modbus_HoldingRegister_BOOL || type == DataType.Modbus_InputRegister_BOOL)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(100) % 2 == 0 ? true : false;
                }
                else
                {
                    obj = Convert.ToBoolean(data);
                }

            }
            else if (type == DataType.AB_DINT || type == DataType.MIT_LONG || type == DataType.Bechoff_DINT)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(Int32.MinValue, Int32.MaxValue);
                }
                else
                {
                    obj = Convert.ToInt32(data);
                }
            }
            else if (type == DataType.AB_INT || type == DataType.SIE_INT || type == DataType.MIT_INT || type == DataType.OPC_SHORT ||
                type == DataType.Modbus_HoldingRegister_WORD || type == DataType.Modbus_InputRegister_WORD)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(Int16.MinValue, Int16.MaxValue);
                }
                else
                {
                    obj = Convert.ToInt16(data);
                }
            }
            else if (type == DataType.AB_SINT || type == DataType.SIE_BYTE || type == DataType.Fanuc_BYTE || type == DataType.OPC_BYTE)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(byte.MinValue, byte.MaxValue);
                }
                else
                {
                    obj = Convert.ToByte(data);
                }
            }
            else if (type == DataType.AB_REAL || type == DataType.MIT_FLOAT || type == DataType.SIE_REAL || type == DataType.Bechoff_REAL || type == DataType.OPC_FLOAT)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = Convert.ToSingle(_rnd.NextDouble());
                }
                else
                {
                    obj = Convert.ToSingle(data);
                }
            }
            else if (type == DataType.AB_STRING || type == DataType.SIE_CHAR || type == DataType.Bechoff_STRING || type == DataType.OPC_STRING)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = System.DateTime.Now.ToString("yyyyMMddHHmmssfff");
                }
                else
                {
                    obj = data;
                }
            }
            else if (type == DataType.MIT_CHAR2)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(byte.MinValue, byte.MaxValue);
                }
                else
                {
                    obj = data.ToCharArray()[0];
                }
            }
            else if (type == DataType.SIE_UINT || type == DataType.MIT_WORD)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(UInt16.MinValue, UInt16.MaxValue);
                }
                else
                {
                    obj = Convert.ToUInt16(data);
                }
            }
            else if (type == DataType.SIE_DINT)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(Int32.MinValue, Int32.MaxValue);
                }
                else
                {
                    obj = Convert.ToInt32(data);
                }
            }
            else if (type == DataType.SIE_UDINT)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    obj = _rnd.Next(Int32.MinValue, Int32.MaxValue);
                }
                else
                {
                    obj = Convert.ToUInt32(data);
                }
            }
            else if (type == DataType.OPC_BYTE_ARRAY)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    throw new Exception("Don't support");
                }
                else
                {

                }
            }
            else if (type == DataType.OPC_FLOAT_ARRAY)
            {
                if (data.ToUpper() == "[RANDOM]")
                {
                    throw new Exception("Don't support");
                }
                else
                {

                }
            }
            else
            {
                throw new Exception("Unknow type");
            }
            return obj;
        }

        protected byte[] GetBytesFromDataType(DataType type, object data)
        {
            byte[] temp;
            if (type == DataType.AB_BOOL || type == DataType.SIE_BOOL || type == DataType.Bechoff_BOOL ||
                type == DataType.Modbus_Coil_BOOL || type == DataType.Modbus_DiscreteInput_BOOL || type == DataType.Modbus_HoldingRegister_BOOL || type == DataType.Modbus_InputRegister_BOOL)
            {
                return BitConverter.GetBytes(Convert.ToBoolean(data.ToString()));
            }
            else if (type == DataType.AB_DINT || type == DataType.Bechoff_DINT)
            {
                return BitConverter.GetBytes(Convert.ToInt32(data.ToString()));
            }
            else if (type == DataType.AB_INT)
            {
                return BitConverter.GetBytes(Convert.ToInt16(data.ToString()));
            }
            else if (type == DataType.AB_SINT)
            {
                return BitConverter.GetBytes(Convert.ToByte(data.ToString()));
            }
            else if (type == DataType.AB_REAL || type == DataType.MIT_FLOAT || type == DataType.Bechoff_REAL)
            {
                return BitConverter.GetBytes(Convert.ToSingle(data.ToString()));
            }
            else if (type == DataType.AB_STRING)
            {
                return System.Text.Encoding.ASCII.GetBytes(data.ToString());
            }
            else if (type == DataType.MIT_WORD || type == DataType.OMR_WORD)
            {
                return BitConverter.GetBytes(Convert.ToUInt16(data.ToString()));
            }
            else if (type == DataType.MIT_INT)
            {
                return BitConverter.GetBytes(Convert.ToInt16(data.ToString()));
            }
            else if (type == DataType.MIT_LONG)
            {
                return BitConverter.GetBytes(Convert.ToInt32(data.ToString()));
            }
            else if (type == DataType.MIT_CHAR2)
            {
                return new byte[] { BitConverter.GetBytes(Convert.ToUInt16(data))[0], BitConverter.GetBytes(Convert.ToUInt16(data))[1] };
            }
            else if (type == DataType.SIE_BYTE || type == DataType.OPC_BYTE)
            {
                return new byte[] { Convert.ToByte(data.ToString()) };
            }
            else if (type == DataType.SIE_UINT)
            {
                temp = BitConverter.GetBytes(Convert.ToUInt16(data.ToString()));
                Array.Reverse(temp);
                return temp;
            }
            else if (type == DataType.SIE_INT)
            {
                temp = BitConverter.GetBytes(Convert.ToInt16(data.ToString()));
                Array.Reverse(temp);
                return temp;
            }
            else if (type == DataType.Modbus_HoldingRegister_WORD || type == DataType.Modbus_InputRegister_WORD)
            {
                temp = BitConverter.GetBytes(Convert.ToInt16(data.ToString()));
                Array.Reverse(temp);
                return temp;
            }
            else if (type == DataType.SIE_DINT)
            {
                temp = BitConverter.GetBytes(Convert.ToInt32(data.ToString()));
                Array.Reverse(temp);
                return temp;
            }
            else if (type == DataType.SIE_UDINT)
            {
                temp = BitConverter.GetBytes(Convert.ToUInt32(data.ToString()));
                Array.Reverse(temp);
                return temp;
            }
            else if (type == DataType.SIE_REAL)
            {
                temp = BitConverter.GetBytes(Convert.ToSingle(data.ToString()));
                Array.Reverse(temp);
                return temp;
            }
            else if (type == DataType.SIE_CHAR)
            {
                return new byte[] { (byte)Convert.ToChar(data.ToString()) };
            }
            else if (type == DataType.Bechoff_STRING)
            {
                return System.Text.Encoding.ASCII.GetBytes(data.ToString());
            }
            return null;
        }

        protected object GetObjectFromBytes(DataType type, byte[] data)
        {
            if (type == DataType.AB_BOOL || type == DataType.SIE_BOOL || type == DataType.Bechoff_BOOL || type == DataType.OPC_BOOLEAN ||
                type == DataType.Modbus_Coil_BOOL || type == DataType.Modbus_DiscreteInput_BOOL || type == DataType.Modbus_HoldingRegister_BOOL || type == DataType.Modbus_InputRegister_BOOL)
            {
                return BitConverter.ToBoolean(data, 0);

            }
            else if (type == DataType.MIT_WORD)
            {
                return BitConverter.ToUInt16(data, 0);
            }
            else if (type == DataType.MIT_INT)
            {
                return BitConverter.ToInt16(data, 0);
            }
            else if (type == DataType.MIT_LONG)
            {
                return BitConverter.ToInt32(data, 0);
            }
            else if (type == DataType.MIT_FLOAT)
            {
                return BitConverter.ToSingle(data, 0);
            }
            else if (type == DataType.MIT_CHAR2)
            {
                return ((char)data[0]).ToString() + ((char)data[1]).ToString();
            }
            else if (type == DataType.SIE_BYTE)
            {
                return data[0];
            }
            else if (type == DataType.SIE_INT)
            {
                Array.Reverse(data);
                return BitConverter.ToInt16(data, 0);
            }
            else if (type == DataType.SIE_UINT)
            {
                Array.Reverse(data);
                return BitConverter.ToUInt16(data, 0);
            }
            else if (type == DataType.SIE_DINT)
            {
                Array.Reverse(data);
                return BitConverter.ToInt32(data, 0);
            }
            else if (type == DataType.SIE_UDINT)
            {
                Array.Reverse(data);
                return BitConverter.ToUInt32(data, 0);
            }
            else if (type == DataType.SIE_REAL)
            {
                Array.Reverse(data);
                return BitConverter.ToSingle(data, 0);
            }
            else if (type == DataType.SIE_CHAR)
            {
                return (char)data[0];
            }
            else if (type == DataType.OMR_WORD)
            {
                return BitConverter.ToUInt16(data, 0);
            }
            else if (type == DataType.Modbus_InputRegister_WORD || type == DataType.Modbus_HoldingRegister_WORD)
            {
                Array.Reverse(data);
                return BitConverter.ToInt16(data, 0);
            }
            return null;
        }

        public static Hashtable GetDataTypeTalbe(PLCType type)
        {
            Hashtable htDataType = null;
            if (type == PLCDriver.PLCType.Allen_Bradley || type == PLCType.MicroLogix)
            {
                htDataType = PLCDriver.ABDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.Mitsubishi)
            {
                htDataType = PLCDriver.MitDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.Siemens)
            {
                htDataType = PLCDriver.SieDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.Omron)
            {
                htDataType = PLCDriver.SieDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.Bechkoff)
            {
                htDataType = PLCDriver.BechoffDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.Fanuc)
            {
                htDataType = PLCDriver.FanucDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.OPC)
            {
                htDataType = PLCDriver.OPCDataTypeTable;
            }
            else if (type == PLCDriver.PLCType.Modbus)
            {
                htDataType = PLCDriver.ModbusDataTypeTable;
            }
            return htDataType;
        }

        public static DataType GetDataType(PLCType type, string dataType)
        {
            try
            {
                Hashtable htDataType = GetDataTypeTalbe(type);
                IDictionaryEnumerator dict = htDataType.GetEnumerator();
                while (dict.MoveNext())
                {
                    if (dict.Value.ToString() == dataType)
                    {
                        return (DataType)dict.Key;
                    }
                }
            }
            catch
            {
            }
            return DataType.UNKNOWN;
        }

        public static string ConvertArrayToString(object[] ary)
        {
            string result = "";
            for (int i = 0; i < ary.Length; i++)
            {
                if (ary[i] == null)
                {
                    ary[i] = "";
                }
                result += ary[i].ToString().Replace("\0", "") + ",";
            }
            if (result != "")
            {
                result = result.Substring(0, result.Length - 1);
                result = "{" + result + "}";
            }
            return result;
        }

        public static string ConvertByteToBinaryStr(byte val)
        {
            int num = val;
            ArrayList list = new ArrayList();
            while (true)
            {
                list.Add(num % 2);
                num = num / 2;
                if (num == 0)
                {
                    break;
                }
            }
            int len = list.Count;
            if (len % 8 != 0)
            {
                for (int i = 0; i < len + 8; i++)
                {
                    if (i >= len)
                    {
                        list.Add("0");
                        if (list.Count % 8 == 0)
                        {
                            break;
                        }
                    }
                }
            }
            list.Reverse();
            string str = "";
            for (int i = 0; i < list.Count; i++)
            {
                str += list[i].ToString();
            }
            return str;
        }

        protected int GetLenFromDataType(PLCDriver.DataType type)
        {
            int len = 0;
            if (type == DataType.SIE_BOOL || type == DataType.SIE_BYTE || type == DataType.SIE_CHAR ||
                type == DataType.Modbus_HoldingRegister_BOOL || type == DataType.Modbus_DiscreteInput_BOOL || type == DataType.Modbus_Coil_BOOL || type == DataType.Modbus_InputRegister_BOOL)
            {
                len = 1;
            }
            else if (type == DataType.SIE_INT || type == DataType.SIE_UINT || type == DataType.MIT_CHAR2 || type == DataType.MIT_WORD || type == DataType.MIT_INT || type == DataType.OMR_WORD ||
                type == DataType.Modbus_HoldingRegister_WORD || type == DataType.Modbus_InputRegister_WORD)
            {
                len = 2;
            }
            else if (type == DataType.SIE_DINT || type == DataType.SIE_UDINT || type == DataType.SIE_REAL || type == DataType.MIT_FLOAT || type == DataType.MIT_LONG)
            {
                len = 4;
            }
            return len;
        }

    }

    public class ByteUtility
    {
        public static string ConvertBytesToHexStr(byte[] data)
        {
            return ConvertBytesToHexStr(data, data.Length);
        }

        public static string ConvertBytesToHexStr(byte[] data, int len)
        {
            string str = "";
            if (data.Length == 0 || len == 0)
            {
                return "";
            }
            for (int i = 0; i < len; i++)
            {
                str += data[i].ToString("X2") + " ";
            }
            return str.Substring(0, str.Length - 1);
        }

        public static byte[] ConvertHexStrToBytes(string str)
        {
            if (str.Trim() == "")
            {
                return new byte[0];
            }
            string[] ary = str.Split(new char[] { ' ' });
            byte[] data = new byte[ary.Length];
            for (int i = 0; i < ary.Length; i++)
            {
                data[i] = byte.Parse(ary[i], System.Globalization.NumberStyles.HexNumber);
            }
            return data;
        }

        public static string ConvertByteToBinaryStr(byte val)
        {
            int num = val;
            ArrayList list = new ArrayList();
            while (true)
            {
                list.Add(num % 2);
                num = num / 2;
                if (num == 0)
                {
                    break;
                }
            }
            int len = list.Count;
            if (len % 8 != 0)
            {
                for (int i = 0; i < len + 8; i++)
                {
                    if (i >= len)
                    {
                        list.Add("0");
                        if (list.Count % 8 == 0)
                        {
                            break;
                        }
                    }
                }
            }
            list.Reverse();
            string str = "";
            for (int i = 0; i < list.Count; i++)
            {
                str += list[i].ToString();
            }
            return str;
        }

        public static float ConvertPLCBytesToFloat(byte[] buf)
        {
            Array.Reverse(buf);
            return BitConverter.ToSingle(buf, 0);
        }

        public static UInt32 ConvertPLCBytesToUInt32(byte[] buf)
        {
            Array.Reverse(buf);
            return BitConverter.ToUInt32(buf, 0);
        }

        public static UInt16 ConvertPLCBytesToUInt16(byte[] buf)
        {
            Array.Reverse(buf);
            return BitConverter.ToUInt16(buf, 0);
        }

        public static string ConvertPLCBytesToStr(byte[] buf)
        {
            return System.Text.Encoding.ASCII.GetString(buf);
        }

        public static byte[] ConvertFloatToPLCBytes(float num)
        {
            byte[] buf = BitConverter.GetBytes(num);
            Array.Reverse(buf);
            return buf;
        }

        public static byte[] ConvertUInt32ToPLCBytes(UInt32 num)
        {
            byte[] buf = BitConverter.GetBytes(num);
            Array.Reverse(buf);
            return buf;
        }

        public static byte[] ConvertUInt16ToPLCBytes(UInt16 num)
        {
            byte[] buf = BitConverter.GetBytes(num);
            Array.Reverse(buf);
            return buf;
        }

        public static byte[] ConvertStrToPLCBytes(string str)
        {
            return System.Text.Encoding.ASCII.GetBytes(str);
        }

        public static UInt16 GetCheckSum(float[] n_real)
        {
            UInt16 checkSum = 0;
            byte[] buf;
            for (int i = 0; i < n_real.Length; i++)
            {
                buf = ConvertFloatToPLCBytes(n_real[i]);
                checkSum ^= (UInt16)(buf[0] << 0x8 | buf[1]);
                checkSum ^= (UInt16)(buf[2] << 0x8 | buf[3]);
            }
            return checkSum;
        }

    }
}
